use crossterm::{
    cursor,
    execute, queue,
    style::{self, Stylize},
    terminal::{self, ClearType},
};
use rand::Rng;
use std::io::{self, Read, Write};
use std::os::unix::io::AsRawFd;
use std::time::{Duration, Instant};

const WIDTH: u16 = 80;
const HEIGHT: u16 = 20;
const GROUND_Y: u16 = HEIGHT - 3;
const DINO_X: u16 = 10;
const CLOUD_MIN_Y: u16 = 2;
const CLOUD_MAX_Y: u16 = HEIGHT / 2;
const BASE_SPEED: f32 = 0.9;
const MAX_SPEED: f32 = 2.2;
const BASE_SPAWN_INTERVAL: f32 = 1.8;

#[derive(Debug, Clone, Copy, PartialEq)]
enum GameState {
    Playing,
    GameOver,
}

#[derive(Debug, Clone, Copy)]
enum ObstacleKind {
    ShortCactus,
    TallCactus,
    Bird,
}

struct Dino {
    y: f32,
    velocity: f32,
    is_jumping: bool,
}

impl Dino {
    fn new() -> Self {
        Dino {
            y: GROUND_Y as f32,
            velocity: 0.0,
            is_jumping: false,
        }
    }

    fn jump(&mut self) {
        if !self.is_jumping {
            self.velocity = -1.2;
            self.is_jumping = true;
        }
    }

    fn update(&mut self) {
        self.velocity += 0.08; // gravity
        self.y += self.velocity;

        if self.y >= GROUND_Y as f32 {
            self.y = GROUND_Y as f32;
            self.velocity = 0.0;
            self.is_jumping = false;
        }
    }

    fn get_y(&self) -> u16 {
        self.y as u16
    }
}

struct Obstacle {
    x: f32,
    y: u16,
    width: u16,
    height: u16,
    kind: ObstacleKind,
}

impl Obstacle {
    fn new(kind: ObstacleKind, x: f32) -> Self {
        let (y, width, height) = match kind {
            ObstacleKind::ShortCactus => (GROUND_Y, 2, 2),
            ObstacleKind::TallCactus => (GROUND_Y - 1, 4, 3),
            ObstacleKind::Bird => (GROUND_Y - 4, 3, 1),
        };

        Obstacle {
            x,
            y,
            width,
            height,
            kind,
        }
    }

    fn update(&mut self, speed: f32) {
        self.x -= speed;
    }

    fn is_off_screen(&self) -> bool {
        self.x < -(self.width as f32) - 2.0
    }

    fn get_x(&self) -> u16 {
        self.x as u16
    }

    fn collides_with(&self, dino: &Dino) -> bool {
        let dino_x = DINO_X;
        let dino_y = dino.get_y();
        let dino_width = 3;
        let dino_height = 2;

        let obs_x = self.x as f32;
        let obs_right = obs_x + self.width as f32;
        let height = f32::from(self.height.max(1));
        let obs_top = self.y as f32 - (height - 1.0);
        let obs_bottom = self.y as f32;

        let dino_left = dino_x as f32;
        let dino_right = dino_left + dino_width as f32;
        let horizontal_overlap = dino_right > obs_x && dino_left < obs_right;

        let dino_top = dino_y as f32;
        let dino_bottom = dino_top + dino_height as f32 - 1.0;
        let vertical_overlap = dino_bottom >= obs_top && dino_top <= obs_bottom;

        horizontal_overlap && vertical_overlap
    }

    fn render(&self, stdout: &mut io::Stdout) -> io::Result<()> {
        if self.x >= WIDTH as f32 {
            return Ok(());
        }
        if self.x + self.width as f32 <= 0.0 {
            return Ok(());
        }

        let obs_x = self.get_x();

        match self.kind {
            ObstacleKind::ShortCactus => {
                queue!(stdout, cursor::MoveTo(obs_x, self.y - 1))?;
                queue!(stdout, style::Print("/\\".bold().red()))?;
                queue!(stdout, cursor::MoveTo(obs_x, self.y))?;
                queue!(stdout, style::Print("||".bold().red()))?;
            }
            ObstacleKind::TallCactus => {
                queue!(stdout, cursor::MoveTo(obs_x, self.y - 2))?;
                queue!(stdout, style::Print(" /\\ ".bold().red()))?;
                queue!(stdout, cursor::MoveTo(obs_x, self.y - 1))?;
                queue!(stdout, style::Print("/||\\".bold().red()))?;
                queue!(stdout, cursor::MoveTo(obs_x, self.y))?;
                queue!(stdout, style::Print(" || ".bold().red()))?;
            }
            ObstacleKind::Bird => {
                queue!(stdout, cursor::MoveTo(obs_x, self.y))?;
                queue!(stdout, style::Print("<^>".bold().yellow()))?;
            }
        }

        Ok(())
    }
}

struct Cloud {
    x: f32,
    y: u16,
    speed: f32,
}

impl Cloud {
    fn new(x: f32) -> Self {
        let mut rng = rand::thread_rng();
        Cloud {
            x,
            y: rng.gen_range(CLOUD_MIN_Y..=CLOUD_MAX_Y),
            speed: rng.gen_range(0.12..0.28),
        }
    }

    fn update(&mut self, dt: f32) {
        self.x -= self.speed * dt * 60.0;
    }

    fn is_off_screen(&self) -> bool {
        self.x < -5.0
    }

    fn render(&self, stdout: &mut io::Stdout) -> io::Result<()> {
        if self.x >= WIDTH as f32 {
            return Ok(());
        }
        let cloud_x = self.x as u16;
        queue!(stdout, cursor::MoveTo(cloud_x, self.y))?;
        queue!(stdout, style::Print("~~~".dark_grey()))?;
        Ok(())
    }
}

struct Game {
    dino: Dino,
    obstacles: Vec<Obstacle>,
    clouds: Vec<Cloud>,
    score: u32,
    high_score: u32,
    speed: f32,
    state: GameState,
    spawn_timer: f32,
    spawn_interval: f32,
    cloud_timer: f32,
}

impl Game {
    fn new() -> Self {
        Game {
            dino: Dino::new(),
            obstacles: Vec::new(),
            clouds: Vec::new(),
            score: 0,
            high_score: 0,
            speed: BASE_SPEED,
            state: GameState::Playing,
            spawn_timer: 0.0,
            spawn_interval: BASE_SPAWN_INTERVAL,
            cloud_timer: 0.0,
        }
    }

    fn update(&mut self, dt: f32) {
        if self.state != GameState::Playing {
            return;
        }

        self.dino.update();

        // Update obstacles
        for obstacle in &mut self.obstacles {
            obstacle.update(self.speed);
        }

        // Remove off-screen obstacles
        self.obstacles.retain(|obs| !obs.is_off_screen());

        // Spawn new obstacles
        self.spawn_timer += dt;
        let mut rng = rand::thread_rng();
        let speed_factor = (self.speed - BASE_SPEED).max(0.0);
        let base_interval = (BASE_SPAWN_INTERVAL - speed_factor * 0.25).max(0.85);
        if self.spawn_timer > self.spawn_interval {
            let x = WIDTH as f32 + rng.gen_range(0.0..20.0);
            let kind = match rng.gen_range(0..3) {
                0 => ObstacleKind::TallCactus,
                1 => ObstacleKind::Bird,
                _ => ObstacleKind::ShortCactus,
            };
            self.obstacles.push(Obstacle::new(kind, x));
            self.spawn_timer = 0.0;
            self.spawn_interval = base_interval + rng.gen_range(0.0..0.7);
        }

        // Update clouds
        self.cloud_timer += dt;
        if self.cloud_timer > 3.5 {
            let x = WIDTH as f32 + rng.gen_range(0.0..30.0);
            self.clouds.push(Cloud::new(x));
            self.cloud_timer = 0.0;
        }

        for cloud in &mut self.clouds {
            cloud.update(dt);
        }
        self.clouds.retain(|cloud| !cloud.is_off_screen());

        // Check collisions
        for obstacle in &self.obstacles {
            if obstacle.collides_with(&self.dino) {
                self.state = GameState::GameOver;
                return;
            }
        }

        // Update score
        self.score += 1;
        if self.score > self.high_score {
            self.high_score = self.score;
        }

        // Increase speed gradually
        if self.score % 250 == 0 {
            self.speed = (self.speed + 0.05).min(MAX_SPEED);
        }
    }

    fn reset(&mut self) {
        self.dino = Dino::new();
        self.obstacles.clear();
        self.clouds.clear();
        self.score = 0;
        self.speed = BASE_SPEED;
        self.state = GameState::Playing;
        self.spawn_timer = 0.0;
        self.spawn_interval = BASE_SPAWN_INTERVAL;
        self.cloud_timer = 0.0;
    }
}

fn render(stdout: &mut io::Stdout, game: &Game) -> io::Result<()> {
    // Clear screen and reset cursor
    queue!(
        stdout,
        cursor::Hide,
        terminal::Clear(ClearType::All),
        cursor::MoveTo(0, 0)
    )?;

    // Draw score
    queue!(
        stdout,
        cursor::MoveTo(2, 1),
        style::Print(format!(
            "Score: {:>6}  Best: {:>6}  Speed: {:.1}",
            game.score, game.high_score, game.speed
        ))
    )?;

    // Draw clouds
    for cloud in &game.clouds {
        cloud.render(stdout)?;
    }

    // Draw ground
    queue!(stdout, cursor::MoveTo(0, GROUND_Y + 1))?;
    for _ in 0..WIDTH {
        queue!(stdout, style::Print("_"))?;
    }

    // Draw dino
    let dino_y = game.dino.get_y();
    queue!(stdout, cursor::MoveTo(DINO_X, dino_y))?;
    queue!(stdout, style::Print("  __".bold().green()))?;
    queue!(stdout, cursor::MoveTo(DINO_X, dino_y + 1))?;
    queue!(stdout, style::Print(" /o)".bold().green()))?;

    // Draw obstacles
    for obstacle in &game.obstacles {
        if obstacle.x >= -4.0 && obstacle.get_x() < WIDTH {
            obstacle.render(stdout)?;
        }
    }

    // Draw game over message
    if game.state == GameState::GameOver {
        let msg = "GAME OVER! Press 'R' to restart or 'Q' to quit";
        let x = (WIDTH - msg.len() as u16) / 2;
        queue!(stdout, cursor::MoveTo(x, HEIGHT / 2))?;
        queue!(stdout, style::Print(msg.bold().red()))?;
        let hint = "Space to jump. Avoid birds by staying grounded!";
        let hint_x = (WIDTH - hint.len() as u16) / 2;
        queue!(stdout, cursor::MoveTo(hint_x, HEIGHT / 2 + 2))?;
        queue!(stdout, style::Print(hint.dark_grey()))?;
    } else {
        let msg = "Press SPACE to jump | Q quits";
        let x = (WIDTH - msg.len() as u16) / 2;
        queue!(stdout, cursor::MoveTo(x, HEIGHT - 1))?;
        queue!(stdout, style::Print(msg.dark_grey()))?;
    }

    // IMPORTANT: Explicitly flush to ensure output appears on UART
    stdout.flush()?;
    Ok(())
}

fn main() -> io::Result<()> {
    let mut stdout = io::stdout();
    let mut stdin = io::stdin();

    // Setup terminal - try without alternate screen for UART compatibility
    terminal::enable_raw_mode()?;

    // Print startup message to verify output is working
    writeln!(stdout, "\r\nStarting Dino Terminal Game...\r")?;
    writeln!(stdout, "Press Q to quit, SPACE to jump\r")?;
    stdout.flush()?;
    std::thread::sleep(Duration::from_secs(2));

    execute!(stdout, terminal::Clear(ClearType::All))?;
    execute!(stdout, cursor::Hide)?;
    stdout.flush()?;

    // Make stdin non-blocking for UART compatibility
    let stdin_fd = stdin.as_raw_fd();
    unsafe {
        let flags = libc::fcntl(stdin_fd, libc::F_GETFL, 0);
        libc::fcntl(stdin_fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
    }

    let mut game = Game::new();
    let mut last_update = Instant::now();

    // Game loop
    loop {
        let now = Instant::now();
        let dt = now.duration_since(last_update).as_secs_f32();
        last_update = now;

        // Handle input with non-blocking read
        let mut buf = [0u8; 1];
        if let Ok(n) = stdin.read(&mut buf) {
            if n > 0 {
                match buf[0] {
                    b'q' | b'Q' | 3 => break, // q, Q, or Ctrl+C
                    b' ' | 0x1b => {
                        // Space or arrow keys (ESC sequences)
                        if game.state == GameState::Playing {
                            game.dino.jump();
                        }
                    }
                    b'r' | b'R' => {
                        if game.state == GameState::GameOver {
                            game.reset();
                        }
                    }
                    _ => {}
                }
            }
        }

        // Update game
        game.update(dt);

        // Render
        render(&mut stdout, &game)?;

        std::thread::sleep(Duration::from_millis(16));
    }

    // Cleanup
    execute!(stdout, cursor::Show)?;
    execute!(stdout, terminal::Clear(ClearType::All))?;
    terminal::disable_raw_mode()?;
    writeln!(stdout, "\r\nGame Over! Thanks for playing.\r")?;

    Ok(())
}
